﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Windows;
using System.Xml.Serialization;
using System.IO;
using nGSO;
using System.Threading;
using System.Threading.Tasks;

namespace nGSO
{
    public delegate void ChangedAgentLocationEventHandler(object sender, AgentChangedLocationEventArgs e);
    public delegate void IterationCompletedHandler(object sender, IterationProgressEventArgs e);
    
    public class GsoEngine : IGsoEngine, IDisposable
    {   
        #region events
        public event ChangedAgentLocationEventHandler AgentMoved;

        protected virtual void OnAgentMoved(AgentChangedLocationEventArgs e)
        {
            if (AgentMoved != null)
                AgentMoved(this, e);
        }

        public event IterationCompletedHandler IterationCompleted;

        protected virtual void OnIterationComplete(IterationProgressEventArgs e)
        {
            if (IterationCompleted != null)
                IterationCompleted(this, e);
        }
        #endregion
        #region Members
        private ManualResetEvent _shutdownEvent = new ManualResetEvent(false);
        private ManualResetEvent _pauseEvent = new ManualResetEvent(true);

        private GsoConfig _config = null;
        private SearchSpace _gsoSpace = null;
        private int _iteration = 1;
        private Stopwatch _sw = null;
        private Random _random = new Random();
        private INeighboursLocalizator _neighboursLocalizator = null;
        
        #endregion
        #region constructors
        public GsoEngine(GsoConfig config)
            : this(new DefaultNeighboursLocalizator(), config)
        {
            
        }

        public GsoEngine(INeighboursLocalizator neighboursLocalizator, GsoConfig config)
        {
            _neighboursLocalizator = neighboursLocalizator;
            _config = config;
            _gsoSpace = new SearchSpace(_config);
            _sw = new Stopwatch();
        }

        public GsoEngine(SearchSpace space, INeighboursLocalizator neighboursLocalizator, GsoConfig config)
        {
            _neighboursLocalizator = neighboursLocalizator;
            _config = config;
            _gsoSpace = space;
            _sw = new Stopwatch();
        }
        #endregion

        public void Pause() 
        { 
            _pauseEvent.Reset();
            CurrentState = GsoEngineStates.Paused;
        }    
        public void Resume() 
        { 
            _pauseEvent.Set();
            CurrentState = GsoEngineStates.Running;
        }

        public GsoEngineStates CurrentState
        {
            get;
            private set;
        }

        public SearchSpace StartGSOSolving()
        {
            CurrentState = GsoEngineStates.Running;

            _gsoSpace.InitAgents();

            while (_iteration <= _config.MaxIterations)
            {
                _pauseEvent.WaitOne(Timeout.Infinite); 
                if (_shutdownEvent.WaitOne(0)) 
                    return null;

                _sw.Restart();
       
                Debug.WriteLine("Luceferin update phase ... " + _iteration);
                UpdateLuminescence();
                
                Debug.WriteLine("Movement phase at iteration ... " + _iteration);
                MovementPhase();

                Debug.WriteLine("Adding history entry ... " + _iteration);
                AddNextHistoryEntry();

                Debug.WriteLine("Processed iteriation " + _iteration + " in " + _sw.ElapsedMilliseconds + " [ms] ");

                OnIterationComplete(new IterationProgressEventArgs(_iteration, _config.MaxIterations, _gsoSpace.Agents));

                _iteration++;
            }

            TrimLastHistoryEntry();

            CurrentState = GsoEngineStates.End;

            //SaveResultsToXml();
            return _gsoSpace;
        }

        private void TrimLastHistoryEntry()
        {
            foreach (var agent in _gsoSpace.Agents)
            {
                agent.History.RemoveAt(_iteration - 1);
            }
        }

        public void Stop()
        {
            _shutdownEvent.Set();
            _pauseEvent.Set();
            CurrentState = GsoEngineStates.Off;
        }

        void UpdateLuminescence()
        {
            Parallel.ForEach(_gsoSpace.Agents, (Agent agent) =>{
                double fitness = _gsoSpace.AgentObjectiveFun(agent, agent.Loc);
                agent.Fitness = fitness; // fitess == objective func value
                agent.Luciferin = (1 - _config.Decay) * agent.History[_iteration-1].Luciferin + _config.LuciferinEnhancement * fitness;
            });
        }

        void MovementPhase()
        {
            Parallel.ForEach(_gsoSpace.Agents, (Agent agent) => {

                _neighboursLocalizator.SetAgentNeighbors(agent, _gsoSpace.Agents, _config.Dims);

                if (agent.Neighbors.Count > 0)
                {
                    Agent agentToMoveToward = _config.NeighbourSelector.GetAgentToMoveToward(agent);
                    _gsoSpace.MoveAgent(agent, agentToMoveToward);
                }
                else
                    agent.NextLoc = agent.Loc;

                double rangeCalc = agent.NeighborhoodRange + _config.BetaConst * (_config.DesiredNoOfNeighbors - agent.Neighbors.Count);
                //The quantity r0 is made equal to rs in all the experiments
                agent.NextNeighborhoodRange = Math.Min(_config.RadialSensorRange, Math.Max(0, rangeCalc));

                RunAfterMoveExtensions(agent);

                OnAgentMoved(new AgentChangedLocationEventArgs(agent));
            });

            
        }

        private void RunAfterMoveExtensions(Agent agent)
        {
            foreach (var extension in _config.AfterAgentMoveExtensions.Where(s=>s.Enabled))
                extension.Apply(agent, _config, _gsoSpace.AgentObjectiveFun);
        }

       
        private void AddNextHistoryEntry()
        {
            foreach (Agent agent in _gsoSpace.Agents)
            {
                agent.History.Add(new AgentHistory() { Step = _iteration });
                agent.NeighborhoodRange = agent.NextNeighborhoodRange;
                agent.Loc = agent.NextLoc;
            }
        }
        #region IDisposable Members

        public void Dispose()
        {
            _pauseEvent.Dispose();
            _shutdownEvent.Dispose();
        }

        #endregion
    }
}